/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */
package com.obixlabs.commons.security;

import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import com.obixlabs.commons.ObixRuntimeException;

/**
 * <p>
 * Utility class for performing secret key encryption and description using the 
 * <i>{@value  #DES_ALGORITHM_NAME}</i> algorithm. This class is built on top of the 
 * standard Java Security API.
 * </p> 
 */
public class ObixDESEncrypter
{	
	/**
	 * <p>
	 * Encryption algorithm name.
	 * </p>
	 */
	private static final String DES_ALGORITHM_NAME = "DES";
	
	/**
	 * <p>
	 * Encapsulation of key specification.
	 * </p> 
	 */
	private DESKeySpec keySpecification;
	
	/**
	 * <p>
	 * Encapsulation of the secret key.
	 * </p>
	 */
	private SecretKey secretKey;
	
	/**
	 * <p>
	 * Decryption cipher created from the secret key.
	 * </p>
	 */
	private Cipher decryptCipher;
	
	/**
	 * <p>
	 * Encryption cipher created from the secret key.
	 * </p> 
	 */
	private Cipher encryptCipher;
	
	/**
	 * <p>
	 * The character-set used by the encrypter. At present, this
	 * class only supports the UTF-8 charset.
	 * </p>
	 */
	private Charset charset;
	
	public ObixDESEncrypter(String encryptionKey)
	{ this (encryptionKey.getBytes()); }
	
	/**
	 * <p>
	 * Creates an instance using the supplied key specification, and backed 
	 * by the <i>{@value #DES_ALGORITHM_NAME}</i> encryption algorithm.
	 * </p>
	 * 
	 * @param encryptionKey	The secret key specification.
	 * @throws ObixRuntimeException	If unable to initialise 
	 * underlying encryption engine, or if the key specification is invalid.
	 */
	public ObixDESEncrypter(byte[] encryptionKey)
	{
		try
		{
			keySpecification=new DESKeySpec (encryptionKey);
			SecretKeyFactory keyFactory=
				SecretKeyFactory.getInstance(DES_ALGORITHM_NAME);
			secretKey = keyFactory.generateSecret(keySpecification);
			decryptCipher=Cipher.getInstance(DES_ALGORITHM_NAME);
			encryptCipher=Cipher.getInstance(DES_ALGORITHM_NAME);
			decryptCipher.init(Cipher.DECRYPT_MODE, secretKey);
			encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey);
		
			charset = Charset.forName("UTF-8");
			
		}catch (InvalidKeyException invalidException){
			throw new ObixRuntimeException(
					"Error initializing encryption engine, please see embedded exception for details.",
						invalidException);
		}catch (NoSuchAlgorithmException noSuchAlgorithm){
			throw new ObixRuntimeException(
					"Error initializing encryption engine, please see embedded exception for details.",
						noSuchAlgorithm);
		}catch (NoSuchPaddingException noSuchPaddingException){
			throw new ObixRuntimeException(
					"Error initializing encryption engine, please see embedded exception for details.",
					noSuchPaddingException);
		}catch (InvalidKeySpecException invalidKeyException){
			throw new ObixRuntimeException(
					"Error initializing encryption engine, please see embedded exception for details.",
					invalidKeyException);
		}		
	}
	
	
	/**
	 * <p>
	 * Encrypts the supplied string using the secret key.
	 * </p>
	 * 
	 * @param unencryptedString The text to encrypt.	
	 * @return	An encrypted representation of the parameter string. 
	 */
	public synchronized String encryptString(String unencryptedString)
	{
		String result;

		Base64 base64Encoder = new Base64();
		try 
		{
			byte [] clearStringUnencrypted = 
						unencryptedString.getBytes(charset);
			byte [] encryptedText = encryptCipher.doFinal(clearStringUnencrypted);			
			result = new String(	base64Encoder.encode(encryptedText),
														charset);
		} 
		catch (IllegalStateException illegalStateException) {
			throw new ObixRuntimeException(illegalStateException);
		} catch (IllegalBlockSizeException illegalBlockSizeException) {
			throw new ObixRuntimeException(illegalBlockSizeException);			
		} catch (BadPaddingException badPaddingException) {
			throw new ObixRuntimeException(badPaddingException);
		}
				
		return result;
	}
	
	
	/**
	 * <p>
	 * Decrypts the supplied string using the secret key.
	 * </p>
	 * 
	 * @param encryptedString The text to decrypt.	
	 * @return	A decryption of the supplied string. 
	 */	
	public synchronized String decryptString(String encryptedString)
	{
		String result;
		
		Base64 decoder = new Base64();
		
		try 
		{
			byte [] encryptedStringData = 
							decoder.decode(encryptedString.getBytes());
			byte [] decryptedString = decryptCipher.doFinal(encryptedStringData);			
			result = new String(decryptedString);
		} 
		catch (IllegalStateException illegalStateException) {
			throw new ObixRuntimeException(illegalStateException);
		} catch (IllegalBlockSizeException illegalBlockSizeException) {
			throw new ObixRuntimeException(illegalBlockSizeException);			
		} catch (BadPaddingException badPaddingException) {
			throw new ObixRuntimeException(badPaddingException);
		} 
				
		return result;
	}//end method def
	
}//end class def